import graphene

from .....discount.models import VoucherCode
from .....order import OrderStatus
from .....order import models as order_models
from .....order.error_codes import OrderErrorCode
from ....tests.utils import assert_no_permission, get_graphql_content

DRAFT_ORDER_BULK_DELETE = """
    mutation draftOrderBulkDelete($ids: [ID!]!) {
        draftOrderBulkDelete(ids: $ids) {
            count
            errors {
                field
                code
                message
            }
        }
    }
"""


def test_delete_draft_orders(
    staff_api_client, order_list, permission_group_manage_orders
):
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    order_1, order_2, *orders = order_list
    order_1.status = OrderStatus.DRAFT
    order_2.status = OrderStatus.DRAFT
    order_1.save()
    order_2.save()

    query = DRAFT_ORDER_BULK_DELETE

    variables = {
        "ids": [graphene.Node.to_global_id("Order", order.id) for order in order_list]
    }
    response = staff_api_client.post_graphql(query, variables)
    content = get_graphql_content(response)

    assert content["data"]["draftOrderBulkDelete"]["count"] == 2
    assert not order_models.Order.objects.filter(
        id__in=[order_1.id, order_2.id]
    ).exists()
    assert order_models.Order.objects.filter(
        id__in=[order.id for order in orders]
    ).count() == len(orders)


def test_delete_draft_orders_by_user_no_channel_access(
    staff_api_client,
    order_list,
    permission_group_all_perms_channel_USD_only,
    channel_PLN,
):
    # given
    permission_group_all_perms_channel_USD_only.user_set.add(staff_api_client.user)
    order_1, order_2, *orders = order_list
    order_1.status = OrderStatus.DRAFT
    order_1.channel = channel_PLN
    order_2.status = OrderStatus.DRAFT
    order_1.save()
    order_2.save()

    query = DRAFT_ORDER_BULK_DELETE

    variables = {
        "ids": [
            graphene.Node.to_global_id("Order", order.id)
            for order in [order_1, order_2]
        ]
    }

    # when
    response = staff_api_client.post_graphql(query, variables)

    # then
    assert_no_permission(response)


def test_delete_draft_orders_by_app(
    app_api_client, order_list, permission_manage_orders
):
    # given
    order_1, order_2, *orders = order_list
    order_1.status = OrderStatus.DRAFT
    order_2.status = OrderStatus.DRAFT
    order_1.save()
    order_2.save()

    query = DRAFT_ORDER_BULK_DELETE

    variables = {
        "ids": [graphene.Node.to_global_id("Order", order.id) for order in order_list]
    }

    # when
    response = app_api_client.post_graphql(
        query, variables, permissions=(permission_manage_orders,)
    )

    # then
    content = get_graphql_content(response)

    assert content["data"]["draftOrderBulkDelete"]["count"] == 2
    assert not order_models.Order.objects.filter(
        id__in=[order_1.id, order_2.id]
    ).exists()
    assert order_models.Order.objects.filter(
        id__in=[order.id for order in orders]
    ).count() == len(orders)


def test_delete_draft_orders_orders_with_transaction_item(
    staff_api_client,
    order_list,
    permission_group_manage_orders,
    transaction_item_generator,
):
    # given
    for order in order_list:
        order.status = OrderStatus.DRAFT
        order.save()
        transaction_item_generator(order_id=order.pk)

    query = DRAFT_ORDER_BULK_DELETE

    ids = [graphene.Node.to_global_id("Order", order.id) for order in order_list]
    variables = {"ids": ids}

    # when
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    response = staff_api_client.post_graphql(query, variables)
    content = get_graphql_content(response)

    # then
    assert "errors" in content["data"]["draftOrderBulkDelete"]
    errors = content["data"]["draftOrderBulkDelete"]["errors"]
    assert len(errors) == 3
    for i, _order in enumerate(order_list):
        assert errors[i]["code"] == OrderErrorCode.INVALID.name
        assert errors[i]["field"] in ids
        assert (
            errors[i]["message"]
            == "Cannot delete order with payments or transactions attached to it."
        )
    assert content["data"]["draftOrderBulkDelete"]["count"] == 0
    assert order_models.Order.objects.filter(
        id__in=[order.id for order in order_list]
    ).count() == len(order_list)


def test_draft_order_bulk_delete_with_voucher_and_include_draft_order_in_voucher_usage_false(
    staff_api_client,
    permission_group_manage_orders,
    order_list,
    voucher,
):
    # given
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    voucher_code = voucher.codes.first()

    for order in order_list:
        order.voucher_code = voucher_code.code
        order.status = OrderStatus.DRAFT
        order.save(update_fields=["status", "voucher_code"])

    order_ids = [order.id for order in order_list]

    channel = order.channel
    channel.include_draft_order_in_voucher_usage = False
    channel.save(update_fields=["include_draft_order_in_voucher_usage"])

    voucher.usage_limit = 1
    voucher.save(update_fields=["usage_limit"])
    assert voucher_code.used == 0

    query = DRAFT_ORDER_BULK_DELETE
    variables = {
        "ids": [graphene.Node.to_global_id("Order", order.id) for order in order_list]
    }

    # when
    response = staff_api_client.post_graphql(query, variables)

    # then
    content = get_graphql_content(response)
    assert content["data"]["draftOrderBulkDelete"]["count"] == len(order_list)
    assert not order_models.Order.objects.filter(id__in=order_ids).exists()

    voucher_code.refresh_from_db()
    assert voucher_code.used == 0


def test_draft_order_bulk_delete_with_voucher_and_include_draft_order_in_voucher_usage_true(
    staff_api_client,
    permission_group_manage_orders,
    order_list,
    voucher_with_many_codes,
):
    # given
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    voucher_codes = []
    voucher = voucher_with_many_codes

    for order, voucher_code in zip(order_list, voucher.codes.all(), strict=False):
        order.voucher_code = voucher_code.code
        order.status = OrderStatus.DRAFT

        voucher_codes.append(voucher_code)
        voucher_code.used = 1

    order_models.Order.objects.bulk_update(order_list, ["voucher_code", "status"])
    VoucherCode.objects.bulk_update(voucher_codes, ["used"])

    order_ids = [order.id for order in order_list]

    channel = order.channel
    channel.include_draft_order_in_voucher_usage = True
    channel.save(update_fields=["include_draft_order_in_voucher_usage"])

    voucher.usage_limit = 1
    voucher.save(update_fields=["usage_limit"])

    query = DRAFT_ORDER_BULK_DELETE
    variables = {
        "ids": [graphene.Node.to_global_id("Order", order.id) for order in order_list]
    }

    # when
    response = staff_api_client.post_graphql(query, variables)

    # then
    content = get_graphql_content(response)
    assert content["data"]["draftOrderBulkDelete"]["count"] == len(order_list)
    assert not order_models.Order.objects.filter(id__in=order_ids).exists()

    for code in voucher_codes:
        code.refresh_from_db()
        assert code.used == 0


MUTATION_DELETE_ORDER_LINES = """
    mutation draftOrderLinesBulkDelete($ids: [ID!]!) {
        draftOrderLinesBulkDelete(ids: $ids) {
            count
            errors {
                field
                message
            }
        }
    }
"""


def test_fail_to_delete_non_draft_order_lines(
    staff_api_client, order_with_lines, permission_group_manage_orders
):
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    order = order_with_lines
    order_lines = list(order.lines.all())
    # Ensure we cannot delete a non-draft order
    order.status = OrderStatus.CANCELED
    order.save()

    variables = {
        "ids": [
            graphene.Node.to_global_id("OrderLine", order_line.id)
            for order_line in order_lines
        ]
    }
    response = staff_api_client.post_graphql(MUTATION_DELETE_ORDER_LINES, variables)

    content = get_graphql_content(response)
    assert "errors" in content["data"]["draftOrderLinesBulkDelete"]
    assert content["data"]["draftOrderLinesBulkDelete"]["count"] == 0


def test_delete_draft_order_lines(
    staff_api_client, order_with_lines, permission_group_manage_orders
):
    permission_group_manage_orders.user_set.add(staff_api_client.user)
    order = order_with_lines
    order_lines = list(order.lines.all())
    # Only lines in draft order can be deleted
    order.status = OrderStatus.DRAFT
    order.save()

    variables = {
        "ids": [
            graphene.Node.to_global_id("OrderLine", order_line.id)
            for order_line in order_lines
        ]
    }

    response = staff_api_client.post_graphql(MUTATION_DELETE_ORDER_LINES, variables)
    content = get_graphql_content(response)

    assert content["data"]["draftOrderLinesBulkDelete"]["count"] == 2
    assert not order_models.OrderLine.objects.filter(
        id__in=[order_line.pk for order_line in order_lines]
    ).exists()


def test_delete_draft_order_lines_by_user_no_channel_access(
    staff_api_client,
    order_with_lines,
    permission_group_all_perms_channel_USD_only,
    channel_PLN,
):
    # given
    permission_group_all_perms_channel_USD_only.user_set.add(staff_api_client.user)
    order = order_with_lines
    order_lines = list(order.lines.all())
    # Only lines in draft order can be deleted
    order.status = OrderStatus.DRAFT
    order.channel = channel_PLN
    order.save(update_fields=["status", "channel"])

    variables = {
        "ids": [
            graphene.Node.to_global_id("OrderLine", order_line.id)
            for order_line in order_lines
        ]
    }

    # when
    response = staff_api_client.post_graphql(MUTATION_DELETE_ORDER_LINES, variables)

    # then
    assert_no_permission(response)


def test_delete_draft_order_lines_by_app(
    app_api_client, order_with_lines, permission_manage_orders
):
    # given
    order = order_with_lines
    order_lines = list(order.lines.all())
    # Only lines in draft order can be deleted
    order.status = OrderStatus.DRAFT
    order.save()

    variables = {
        "ids": [
            graphene.Node.to_global_id("OrderLine", order_line.id)
            for order_line in order_lines
        ]
    }

    # when
    response = app_api_client.post_graphql(
        MUTATION_DELETE_ORDER_LINES, variables, permissions=(permission_manage_orders,)
    )

    # then
    content = get_graphql_content(response)

    assert content["data"]["draftOrderLinesBulkDelete"]["count"] == 2
    assert not order_models.OrderLine.objects.filter(
        id__in=[order_line.pk for order_line in order_lines]
    ).exists()
